package org.zyj.io.example.discard;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * 例1:
 * 一个最简单的demo
 * 接收到消息, 直接抛弃
 *
 * <a herf="https://netty.io/wiki/user-guide-for-4.x.html">参考资料</a>
 */
public class DiscardServer {

    public static final int SERVER_PORT = 11223;

    public static void main(String[] args) throws Throwable {
        run();
    }

    public static void run() throws Throwable {
        //NioEventLoopGroup 是一种多线程事件处理器, 用来处理IO各种事件
        //服务端一般使用两个
        //第一个是boss, 用来处理 连接 的各种事件 (一般指TCP连接)
        //第二个是worker, boss会把对应的事件发送到worker上, 由worker进行处理
        //可以在构造器中指定NioEventLoopGroup的处理线程数
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //ServerBootstrap是用来帮助启动服务的类
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    //这里指定: 我们在新的连接来的时候, 要创建 NioServerSocketChannel 来表示一个连接
                    .channel(NioServerSocketChannel.class)
                    //ChannelInitializer是用来帮助用户配置新的channel的
                    //很多时候, 你想要通过添加各种Handler, 来配置新Channel的ChannelPipeline, 用以实现各种网络服务的功能
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            //这里只设置了一个DiscardServerHandler
                            //当你的网络应用需要更复杂的功能的时候, 可以通过增加handler来添加各种功能
                            ch.pipeline().addLast(new DiscardServerHandler());
                        }
                    })

                    //option(), 针对boss线程组, 这里可以设置很多socket相关的参数
                    //参考https://netty.io/4.1/api/io/netty/channel/ChannelOption.html
                    //ChannelOption.SO_BACKLOG:
                    // 设置BACKLOG
                    // 用于构造服务端套接字ServerSocket对象，
                    // 标识当服务器请求处理线程全满时，
                    // 用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1，将使用默认值50。
                    .option(ChannelOption.SO_BACKLOG, 128)

                    //childOption(), 针对worker线程组
                    //ChannelOption.SO_KEEPALIVE:
                    // Socket参数，连接保活，默认值为False。
                    // 启用该功能时，TCP会主动探测空闲连接的有效性。
                    // 可以将此功能视为TCP的心跳机制，
                    // 需要注意的是：默认的心跳间隔是7200s即2小时。Netty默认关闭该功能。
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(SERVER_PORT).sync();

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    //ChannelInboundHandlerAdapter 是 ChannelInboundHandler. ChannelInboundHandler的实现类
    //discard demo 中继承这个adapter就足够了
    public static class DiscardServerHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            /*
                我们可以重写channelRead()这个event的handler
                这个方法, 当接收到消息的时候就会被调用
                无论何时,只要从客户端接收到消息, 就会调用这个方法
                接收到的信息,会存在ByteBuf中
             */
            System.out.println("有输入");
            ((ByteBuf) msg).release(); //这里不做任何操作
            // Please keep in mind that it is the handler's responsibility to release any reference-counted object passed to the handler
            //要记住, handler别忘了在使用完数据之后,要对各种引用资源进行释放
            //一般可以使用如下写法:
            //  try {
            //      //do something with msg
            //  }finally {
            //      ReferenceCountUtil.release(msg);
            //  }
        }

        // The exceptionCaught() event handler method is called with a Throwable when an exception was raised by Netty
        // due to an I/O error or by a handler implementation
        // due to the exception thrown while processing events.
        // In most cases, the caught exception should be logged and its associated channel should be closed here,
        // although the implementation of this method can be different depending on what you want to do to deal with an exceptional situation.
        // For example, you might want to send a response message with an error code before closing the connection.
        //当发生IO错误,或者实现的handler发生异常,或者处理事件的时候抛出异常, 则会调用这个方法exceptionCaught()
        //大对数情况最好用日志记录并将channel关闭
        //不过你也可以根据自己的需求实现一些自定义的逻辑
        //比如返回一个错误信息给连接
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
            // Close the connection when an exception is raised.
            cause.printStackTrace();
            ctx.close();
        }
    }
}
